﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using Mindfor.Web.Config;
using Mindfor.Web.Data;
using Mindfor.Web.Routing;
using NHibernate;
using NHibernate.Linq;
using System.Diagnostics;

namespace Mindfor.Web.Modules.Routes
{
	/// <summary>
	/// Registers routes to pages using data provider and route attributes.
	/// </summary>
	public class RoutesModule : ModuleBase
	{
		public RoutesModule()
		{
			IsAlwaysEnabled = true;
		}

		/// <summary>
		/// Returns modules names current one dependent on.
		/// </summary>
		public override string[] GetDependencyModules()
		{
			return new string[] { CmsConstants.SystemModuleName, "Pages" };
		}

		/// <summary>
		/// Called when all modules was registered.
		/// </summary>
		/// <param name="info">Registration data.</param>
		/// <param name="registeredModules">Registered modules.</param>
		public override void AfterRegister(ModuleRegistrationData info, ModuleBase[] registeredModules)
		{
			// get all pages
			Page[] pages = info.Data.Query<Page>().ToArray();
			
			// get languages
			List<Language> languages = info.Data.Query<Language>().ToList();
			string languageConstraint = languages.Count > 1 ? "(" + languages.Select(r => r.UrlName).Merge("|") + ")" : null;

			// process every module
			foreach (ModuleBase module in registeredModules)
			{
				ActionRouteInfo[] actionRoutes = module.GetActionRoutes();
				foreach (ActionRouteInfo mainActionRoute in actionRoutes.Where(r => String.IsNullOrEmpty(r.MainAction)))
				{
					// register main page
					Page mainActionPage = RegisterRouteInfo(info.Routes, pages, mainActionRoute, languageConstraint, null, null);

					// register sub pages
					ActionRouteInfo[] subActionRoutes = actionRoutes.Where(r =>
						r.ActionPath.ModuleName == mainActionRoute.ActionPath.ModuleName &&
						r.ActionPath.ControllerName == mainActionRoute.ActionPath.ControllerName &&
						r.MainAction == mainActionRoute.ActionPath.ActionName)
						.ToArray();
					foreach (ActionRouteInfo subActionRoute in subActionRoutes)
						RegisterRouteInfo(info.Routes, pages, subActionRoute, languageConstraint, mainActionRoute.ActionPath.ActionName, mainActionPage);
				}
			}

			// register text pages
			foreach (Page page in pages)
			{
				ActionPath actionPath = page.ActionPath;
				if (actionPath == null)
					actionPath = new ActionPath();

				if (actionPath.IsEmpty())
				{
					string url = page.GetPath();
					var defaults = new { pageId = page.Id };

					if (languageConstraint != null)
					{
						string urlLang = RouteHelper.Combine("{lang}", url);
						var constraints = new { lang = languageConstraint };
						MapRoute(info.Routes, actionPath, null, urlLang, defaults, constraints);
					}
					MapRoute(info.Routes, actionPath, null, url, defaults, null);
				}
			}
		}

		/// <summary>
		/// Registers routes for action.
		/// </summary>
		/// <param name="routes">RoutesCollection.</param>
		/// <param name="pages">Pages from data provider.</param>
		/// <param name="actionRoute">Action route information to register.</param>
		/// <param name="languageConstraint">Language constraint. Null if ony one language.</param>
		/// <param name="mainActionName">For main-action is null. For sub-action pass name of main-action.</param>
		/// <param name="mainActionPage">Main action page for sub page. For main page pass null.</param>
		/// <returns>Page associated with action for main action. For sub action returns null.</returns>
		private Page RegisterRouteInfo(RouteCollection routes, Page[] pages, ActionRouteInfo actionRoute, string languageConstraint,
			string mainActionName, Page mainActionPage)
		{
			// get action, pageId, pagePath
			ActionDescriptor action = actionRoute.ActionPath.GetAction().FirstOrDefault();
			bool isMainAction = String.IsNullOrEmpty(mainActionName);
			
			Page page = null;
			int? pageId = null;
			string pagePath = null;
			if (isMainAction)
			{
				List<Page> pagesForAction = pages.Where(p => p.ActionPath == actionRoute.ActionPath).ToList();
				page = pagesForAction.FirstOrDefault();
				if (page != null)
				{
					pageId = page.Id;
					pagePath = page.GetPath();
				}
			}
			else if (mainActionPage != null)
			{
				page = mainActionPage;
				pageId = mainActionPage.Id;
				pagePath = mainActionPage.GetPath();
			}

			// register each action route
			foreach (UrlRouteInfo routeInfo in actionRoute.Routes)
			{
				string path;
				if (routeInfo.Path != null)
					path = routeInfo.Path;
				else if (pagePath != null)
					path = pagePath;
				else
					path = actionRoute.ActionPath.ControllerName;
				
				string name = routeInfo.Name;
				string url = RouteHelper.Combine(path, routeInfo.SubPath == null ? actionRoute.ActionPath.ActionName : routeInfo.SubPath);
				RouteValueDictionary defaults = routeInfo.Defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(routeInfo.Defaults);
				if (isMainAction)
				{
					if (pageId.HasValue)
						defaults["pageId"] = pageId;
				}
				else
				{
					defaults["mainAction"] = mainActionName;
					if (pageId.HasValue)
						defaults["mainActionPageId"] = pageId;
				}
				RouteValueDictionary constraints = routeInfo.Constraints;
				
				if (languageConstraint != null)
				{
					string nameLang = String.IsNullOrEmpty(name) ? name : name + "Language";
					string urlLang = RouteHelper.Combine("{lang}", url);
					RouteValueDictionary constraintsLang = new RouteValueDictionary(constraints);
					constraintsLang["lang"] = languageConstraint;
					MapRoute(routes, actionRoute.ActionPath, nameLang, urlLang, defaults, constraintsLang);
				}
				MapRoute(routes, actionRoute.ActionPath, name, url, defaults, constraints);
			}

			// return associated page
			if (isMainAction)
				return page;
			return null;
		}

		private static int s_nonameRouteNumber = 1;

		/// <summary>
		/// Maps the URL route.
		/// </summary>
		/// <param name="routes">Route collection to map to.</param>
		/// <param name="name">Option route name.</param>
		/// <param name="url">Route URL.</param>
		/// <param name="actionInfo">Module, controller and action information to map to.</param>
		/// <param name="defaults">Defaults object. Can be RouteValueDictonary.</param>
		/// <param name="constraints">Constraints object. Can be RouteValueDictonary.</param>
		protected virtual void MapRoute(RouteCollection routes, ActionPath actionInfo,
			string name, string url, object defaults, object constraints)
		{
			ActionDescriptor action = actionInfo.GetAction().FirstOrDefault();

			// default, constraints, data tokens
			RouteValueDictionary defaultsDic = defaults is RouteValueDictionary ?
				(RouteValueDictionary)defaults :
				new RouteValueDictionary(defaults);
			defaultsDic["module"] = actionInfo.GetModuleNameOrDefault();
			defaultsDic["controller"] = action.ControllerDescriptor.ControllerName;
			defaultsDic["action"] = action.ActionName;

			RouteValueDictionary constraintsDic = constraints is RouteValueDictionary ?
				(RouteValueDictionary)constraints :
				new RouteValueDictionary(constraints);

			RouteValueDictionary dataTokens = new RouteValueDictionary();
			dataTokens.Add("namespaces", new string[] { action.ControllerDescriptor.ControllerType.Namespace });

			// generate name
			if (String.IsNullOrEmpty(name))
				name = "Route" + s_nonameRouteNumber++;

			// map route
			Route route = new Route(url, defaultsDic, constraintsDic, dataTokens, new MvcRouteHandler());
			routes.Add(name, route);
		}
	}
}
